# 分类器的不确定度估计
"""
在 scikit-learn 中，decision_function 和 predict_proba 两个函数可用于获取分类器的不确定度估计。
大多数分类器至少有其中一个，很多分类器两者都有。我们以 GradientBoostingClassifier 为例，它同时具备这两个方法。
"""
from random import random

import matplotlib.pyplot as plt
import mglearn.tools
import numpy as np
# 看一下这两个函数对一个模拟的二维数据集的作用：
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.datasets import make_circles
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split

X, y = make_circles(noise=0.25,factor=0.5,random_state=1)
# 为了便于说明，我们将两个类别重命名为"blue"和"red"
y_named = np.array(["blue", "red"])[y]
# 我们可以对任意个数组调用train_test_split
# 所有数组的划分方式都是一致的
X_train, X_test, y_train_named, y_test_named, y_train, y_test = \
 train_test_split(X, y_named, y, random_state=0)
# 构建梯度提升模型
gbrt = GradientBoostingClassifier(random_state=0)
gbrt.fit(X_train, y_train_named)

# 决策函数

# 对于二分类的情况，decision_function 返回值的形状是 (n_samples,)，为每个样本都返回
# 一个浮点数：
print("X_test.shape: {}".format(X_test.shape))
print("Decision function shape: {}".format(gbrt.decision_function(X_test).shape))
# X_test.shape: (25, 2)
# Decision function shape: (25,)

'''
对于类别 1 来说，这个值表示模型对该数据点属于“正”类的置信程度。
正值表示对正类的偏好，负值表示对“反类”（其他类）的偏好
'''

# 显示decision_function的前几个元素
print("Decision function:\n{}".format(gbrt.decision_function(X_test)[:6]))
# Decision function: [ 4.136 -1.683 -3.951 -3.626 4.29 3.662]

# 可以通过仅查看决策函数的正负号来再现预测值：
print("Thresholded decision function:\n{}".format(
 gbrt.decision_function(X_test) > 0))
print("Predictions:\n{}".format(gbrt.predict(X_test)))
# Decision function:
# [ 4.13592603 -1.70169917 -3.95106099 -3.62609552  4.28986642  3.66166081]
# Thresholded decision function:
# [ True False False False  True  True False  True  True  True False  True
#   True False  True False False False  True  True  True  True  True False
#  False]
# Predictions:
# ['red' 'blue' 'blue' 'blue' 'red' 'red' 'blue' 'red' 'red' 'red' 'blue'
#  'red' 'red' 'blue' 'red' 'blue' 'blue' 'blue' 'red' 'red' 'red' 'red'
#  'red' 'blue' 'blue']

'''
对于二分类问题，“反”类始终是 classes_ 属性的第一个元素，“正”类是 classes_ 的第二个元素。
因此，如果你想要完全再现 predict 的输出，需要利用 classes_ 属性：
'''

# 将布尔值True/False转换成0和1
greater_zero = (gbrt.decision_function(X_test) > 0).astype(int)
# 利用0和1作为classes_的索引
pred = gbrt.classes_[greater_zero]
# pred与gbrt.predict的输出完全相同
print("pred is equal to predictions: {}".format(
 np.all(pred == gbrt.predict(X_test))))

# pred is equal to predictions: True

'''
decision_function 可以在任意范围取值，这取决于数据与模型参数：
'''

decision_function = gbrt.decision_function(X_test)
print("Decision function minimum: {:.2f} maximum: {:.2f}".format(
 np.min(decision_function), np.max(decision_function)))

# Decision function minimum: -7.69 maximum: 4.29

'''
decision_function 的输出可以任意缩放，因此难以解释。在下面的例子中，我们会用颜色编码在二维平面上绘制所有点的 decision_function 值，
以及决策边界。训练数据用圆表示，测试数据用三角形表示。
'''
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
mglearn.tools.plot_2d_separator(gbrt, X, ax=axes[0], alpha=.4,
 fill=True, cm=mglearn.cm2)
scores_image = mglearn.tools.plot_2d_scores(gbrt, X, ax=axes[1],
 alpha=.4, cm=mglearn.ReBl)
for ax in axes:
 # 画出训练点和测试点
 mglearn.discrete_scatter(X_test[:, 0], X_test[:, 1], y_test,
 markers='^', ax=ax)
 mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train,
 markers='o', ax=ax)
 ax.set_xlabel("Feature 0")
 ax.set_ylabel("Feature 1")
cbar = plt.colorbar(scores_image, ax=axes.tolist())
axes[0].legend(["Test class 0", "Test class 1", "Train class 0",
 "Train class 1"], ncol=4, loc=(.1, 1.1))
# plt.show()

# 预测概率
'''
predict_proba 的输出是每个类别的概率，通常比 decision_function 的输出更容易理解。
对于二分类问题，它的形状始终是 (n_samples, 2)
'''
print("Shape of probabilities: {}".format(gbrt.predict_proba(X_test).shape))
# Shape of probabilities: (25, 2)

'''
每行的第一个元素是第一个类别的估计概率，第二个元素是第二个类别的估计概率。由于
predict_proba 的输出是一个概率，因此总是在 0 和 1 之间，两个类别的元素之和始终为 1：
'''

# 显示predict_proba的前几个元素
print("Predicted probabilities:\n{}".format(gbrt.predict_proba(X_test[:6])))
# Predicted probabilities:
# [[0.01573626 0.98426374]
#  [0.84575653 0.15424347]
#  [0.98112869 0.01887131]
#  [0.97407033 0.02592967]
#  [0.01352142 0.98647858]
#  [0.02504637 0.97495363]]


# 由于两个类别的概率之和为 1，因此只有一个类别的概率超过 50%。这个类别就是模型的预测结果。

'''
从上一个输出可以看出，分类器对大部分点的置信度较高。这种不确定度反映了模型和参数对数据的依赖性。
过拟合的模型可能给出高置信度的预测，但这些预测可能并不准确。相反，复杂度较低的模型通常会有更高的预测不确定度。
如果模型的不确定度与实际情况相符，那么它被称为校正（calibrated）模型。
在校正模型中，如果预测的置信度为70%，那么它在70%的情况下是正确的。
'''

# 在下面的例子中（图 2-56），我们再次给出该数据集的决策边界，以及类别 1 的类别概率：
fig,axes = plt.subplots(1, 2, figsize=(13, 5))
mglearn.tools.plot_2d_separator(gbrt, X, ax=axes[0], alpha=.4,fill=True, cm=mglearn.cm2)
scores_image = mglearn.tools.plot_2d_scores(gbrt, X, ax=axes[1],alpha=.5,cm=mglearn.ReBl,function='predict_proba')
for ax in axes:
 mglearn.discrete_scatter(X_test[:, 0], X_test[:, 1], y_test,
                          markers='^', ax=ax)
 mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train,
                          markers='o', ax=ax)
 ax.set_xlabel("Feature 0")
 ax.set_ylabel("Feature 1")
cbar = plt.colorbar(scores_image, ax=axes.tolist())
axes[0].legend(["Test class 0", "Test class 1", "Train class 0",
                "Train class 1"], ncol=4, loc=(.1, 1.1))
# plt.show()

# 多分类问题的不确定度
'''
到目前为止，我们只讨论了二分类问题中的不确定度估计，但 decision_function 和 predict_proba 也适用于多分类问题。
我们将这两个函数应用于鸢尾花（Iris）数据集，这是一个三分类问题。
'''

from sklearn.datasets import load_iris
iris = load_iris()
X_train,X_test,y_train,y_test = train_test_split(iris.data,iris.target,random_state=42)
gbrt = GradientBoostingClassifier(learning_rate=0.01,random_state=0)
gbrt.fit(X_train,y_train)
print("Decision function shape: {}".format(gbrt.decision_function(X_test).shape))
# 显示决策函数的前几个元素
print("Decision function:\n{}".format(gbrt.decision_function(X_test)[:6, :]))

# Decision function shape: (38, 3)
# Decision function:
# [[-0.89604809  1.14724958 -0.82754003]
#  [ 1.16113086 -0.80789044 -0.82827067]
#  [-0.89091511 -0.77671169  1.19653416]
#  [-0.89604809  1.14724958 -0.82754003]
#  [-0.89763468  0.96497584 -0.10374792]
#  [ 1.16113086 -0.80789044 -0.82827067]]

'''
对于多分类问题，decision_function 的输出形状为 (n_samples, n_classes)，每一列对应一个类别的“确定度分数”。
分数越高，表明该类别越有可能是预测结果。你可以通过找出每个数据点的最大分数，来重现预测结果。
'''
print("Argmax of decision function:\n{}".format(
 np.argmax(gbrt.decision_function(X_test), axis=1)))
print("Predictions:\n{}".format(gbrt.predict(X_test)))

'''
predict_proba 输出的形状相同，也是 (n_samples, n_classes)。同样，每个数据点所有可能类别的概率之和为 1：
'''

# 显示predict_proba的前几个元素
print("Predicted probabilities:\n{}".format(gbrt.predict_proba(X_test)[:6]))
# 显示每行的和都是1
print("Sums: {}".format(gbrt.predict_proba(X_test)[:6].sum(axis=1)))

# 同样，我们可以通过计算 predict_proba 的 argmax 来再现预测结果：
print("Argmax of predicted probabilities:\n{}".format(
 np.argmax(gbrt.predict_proba(X_test), axis=1)))
print("Predictions:\n{}".format(gbrt.predict(X_test)))

'''
总结：predict_proba 和 decision_function 的形状通常是 (n_samples, n_classes)，
但在二分类中，decision_function 只有一列（对应“正”类），这是由于历史原因。
可以通过每行的最大值索引（argmax）重现预测结果，但需注意类别为字符串或非连续整数时可能出现问题。
因此，对比预测结果时，应通过分类器的 classes_ 属性获取真实类别名称。
'''

logreg = LogisticRegression()
# 用Iris数据集的类别名称来表示每一个目标值
named_target = iris.target_names[y_train]
logreg.fit(X_train, named_target)
print("unique classes in training data: {}".format(logreg.classes_))
print("predictions: {}".format(logreg.predict(X_test)[:10]))
argmax_dec_func = np.argmax(logreg.decision_function(X_test), axis=1)
print("argmax of decision function: {}".format(argmax_dec_func[:10]))
print("argmax combined with classes_: {}".format(
 logreg.classes_[argmax_dec_func][:10]))

# unique classes in training data: ['setosa' 'versicolor' 'virginica']
# predictions: ['versicolor' 'setosa' 'virginica' 'versicolor' 'versicolor' 'setosa'
#  'versicolor' 'virginica' 'versicolor' 'versicolor']
# argmax of decision function: [1 0 2 1 1 0 1 2 1 1]
# argmax combined with classes_: ['versicolor' 'setosa' 'virginica' 'versicolor' 'versicolor' 'setosa'
#  'versicolor' 'virginica' 'versicolor' 'versicolor']


'''
总结

最近邻
 适用于小型数据集，是很好的基准模型，很容易解释。
线性模型
 非常可靠的首选算法，适用于非常大的数据集，也适用于高维数据。
朴素贝叶斯
 只适用于分类问题。比线性模型速度还快，适用于非常大的数据集和高维数据。精度通
 常要低于线性模型。
决策树
 速度很快，不需要数据缩放，可以可视化，很容易解释。
随机森林
 几乎总是比单棵决策树的表现要好，鲁棒性很好，非常强大。不需要数据缩放。不适用
 于高维稀疏数据。
梯度提升决策树
 精度通常比随机森林略高。与随机森林相比，训练速度更慢，但预测速度更快，需要的
 内存也更少。比随机森林需要更多的参数调节。
支持向量机
 对于特征含义相似的中等大小的数据集很强大。需要数据缩放，对参数敏感。
神经网络
 可以构建非常复杂的模型，特别是对于大型数据集而言。对数据缩放敏感，对参数选取
 敏感。大型网络需要很长的训练时间。

'''
